#include "head.h"


/**
 * @brief 计算文件的 MD5 哈希值
 * @param filename 文件名
 * @param md5_result 存储结果的缓冲区（至少 33 字节，用于 32 字符 + '\0'）
 * @return 成功返回 0，失败返回 -1
 */
//int calculate_md5_bad(const char *filename, char *md5_result) {
//    FILE *file = fopen(filename, "rb");
//    if (!file) {
//        perror("文件打开失败");
//        return -1;
//    }
//
//    MD5_CTX md5_context;
//    MD5_Init(&md5_context);  // 初始化 MD5 上下文
//
//    // 分块读取文件并更新哈希
//    unsigned char buffer[8192];
//    size_t bytes_read;
//    while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
//        MD5_Update(&md5_context, buffer, bytes_read);
//    }
//
//    // 计算最终哈希值
//    unsigned char digest[MD5_DIGEST_LENGTH];  // MD5 结果为 16 字节
//    MD5_Final(digest, &md5_context);
//
//    // 将二进制哈希转换为十六进制字符串
//    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
//        sprintf(md5_result + (i * 2), "%02x", digest[i]);
//    }
//    md5_result[MD5_DIGEST_LENGTH * 2] = '\0';  // 添加字符串结束符
//
//    fclose(file);
//    return 0;
//}

int calculate_md5(const char *filename, char *md5_result) {
    FILE *file = fopen(filename, "rb");
    if (!file) return -1;

    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx) {
        fclose(file);
        return -1;
    }

    if (EVP_DigestInit_ex(ctx, EVP_md5(), NULL) != 1) {
        EVP_MD_CTX_free(ctx);
        fclose(file);
        return -1;
    }

    unsigned char buf[4096];
    size_t bytes;
    while ((bytes = fread(buf, 1, sizeof(buf), file)) > 0) {
        if (EVP_DigestUpdate(ctx, buf, bytes) != 1) {
            EVP_MD_CTX_free(ctx);
            fclose(file);
            return -1;
        }
    }

    unsigned char digest[EVP_MAX_MD_SIZE];
    unsigned int len;
    if (EVP_DigestFinal_ex(ctx, digest, &len) != 1) {
        EVP_MD_CTX_free(ctx);
        fclose(file);
        return -1;
    }

    for (unsigned int i = 0; i < len; i++) {
        sprintf(md5_result + (i * 2), "%02x", digest[i]);
    }
    md5_result[len * 2] = '\0';

    EVP_MD_CTX_free(ctx);
    fclose(file);
    return 0;
}


// 功能：上传
//int putsFile(int sockfd, char* serverPath, char* clientPath){
//    // 计算文件MD5值
//    char md5Num[33];
//    bzero(md5Num, sizeof(md5Num));
//
//    int ret = calculate_md5(clientPath, md5Num);
//    if(ret == -1){
//        printf("file error\n");
//        return -1;
//    }
//    
//    int fd = open(clientPath, O_RDWR);
//    ERROR_CHECK(fd, -1, "open");
//    
//    int fileSize = lseek(fd, 0, SEEK_END);
//    lseek(fd, 0, SEEK_SET);
//    
//    // 先将文件的路径和文件MD5值发送
//    TLVRequest(sockfd, serverPath, PUTS);
//    TLVRequest(sockfd, md5Num, PUTS);
//    TLVRequestLen(sockfd, fileSize);
//    // 获取服务的响应
//    train_t mess;
//    TLVRecvResponse(sockfd, &mess);
//    if(mess.type == IS_OK){
//        // 已存在此文件，秒传成功
//        printf("server talk:%s\n", mess.buf);
//        return 0;
//    }
//    // 错误信息
//    if(mess.type == IS_NOT_OK){
//        printf("error:%s", mess.buf);
//        return -1;
//    }
//
//    // 文件没有，或这没传完，服务端返回已有文件长度
//    off_t curFileSize = mess.len;
//    printf("server file len : %d\n", mess.len);
//    // 告诉服务端，我将要传送多少字节数据
//    TLVRequestLen(sockfd, fileSize - curFileSize);
//    
//    ftruncate(fd, fileSize);
//
//    // 发送数据
//    sendfile(sockfd, fd, &curFileSize, fileSize - curFileSize);
//    close(fd);
//
//    // 接收服务段的响应
//    TLVRecvResponse(sockfd, &mess);
//    printf("server talk :%s\n", mess.buf);
//
//    if(mess.type == IS_OK){
//        return 0;
//    }
//    return -1;
//}


// 文件上传：
int putsFile(int sockfd, char* serverPath, char* clientPath){
    // 计算文件MD5值
    char md5Num[33];
    bzero(md5Num, sizeof(md5Num));

    int ret = calculate_md5(clientPath, md5Num);
    if(ret == -1){
        printf("file error\n");
        return -1;
    }

    int fd = open(clientPath, O_RDWR);
    ERROR_CHECK(fd, -1, "open");

    int fileSize = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET);

    // 文件路径和文件名发送
    TLVRequest(sockfd, serverPath, PUTS);

    // 接收来自服务端路径是否合法信息
    train_t mess;
    TLVRecvResponse(sockfd, &mess);
    // 错误信息
    if(mess.type != IPADDR_PORT){
        printf("server talk:%s\n", mess.buf);
        return -1;
    }


    // mess的buf中是ip和端口
    // 传一个MD5让服务插表
    TLVRequest(sockfd, md5Num, PUTS);

    // ip 和端口切割
    char ip[1024];
    char port[256];
    bzero(ip, sizeof(ip));
    bzero(port, sizeof(port));

    // 切割出IP和端口
    char *token = strtok(mess.buf, "/");
    strcpy(ip, token);
    token = strtok(NULL, "/");
    strcpy(port, token);
    printf("ip :--%s--, port : --%s--\n",ip, port);

    // 与下载服务器建立连接
    int putsfd = establishConnect(ip, port);


    // 下载服务器发送文件md5值以及文件大小
    TLVRequest(putsfd, md5Num, PUTS);
    TLVRequestLen(putsfd, fileSize);
    // 获取服务的响应
    TLVRecvResponse(putsfd, &mess);

    off_t serverCurFileSize = atoi(mess.buf);
    if(mess.len == fileSize){
        // 已存在此文件，秒传成功
        printf("puts success\n");
        return 0;
    }

    // 文件没有，或这没传,
    printf("server file len : %d\n", mess.len);


    ftruncate(fd, fileSize);

    // 发送数据
    sendfile(putsfd, fd, &serverCurFileSize, fileSize - serverCurFileSize);
    close(fd);
    close(putsfd);

    printf("puts success\n");
    return 0;
}



// 功能：下载
//int getsFile(int sockfd, char* clientPath, char* serverPath){
//    
//    int fd = open(clientPath, O_RDWR | O_CREAT, 0666);
//    ERROR_CHECK(fd, -1, "open");
//
//    TLVRequest(sockfd, serverPath, GETS);
//
//    // 查看文件大小，若已有部分文件，询问是否断点重传
//    int fileSize = lseek(fd, 0, SEEK_END);
//    lseek(fd, 0, SEEK_SET);
//    
//    char ch;
//    if(fileSize != 0){
//        printf("Retransmission from breakpoint?[y/n]\n");
//        scanf(" %c", &ch);
//        if(ch != 'y'){
//            fileSize = 0;
//        }
//    }
//    // 告知服务端，已有文件大小
//    TLVRequestLen(sockfd, fileSize);
//    // 接收服务段将要发送文件大小
//    train_t mess;
//    TLVRecvResponse(sockfd, &mess);
//    
//    // 服务端不存在此文件
//    if(mess.type != FILE_LEN){
//        printf("server talk : %s\n", mess.buf);
//        return -1;
//    }
//
//    int recvFileSize = mess.len;
//
//    ftruncate(fd, fileSize + recvFileSize);
//
//    char *buf = (char *)mmap(NULL, fileSize + recvFileSize, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
//
//    recv(sockfd, buf + fileSize, recvFileSize, MSG_WAITALL);
//    
//    munmap(buf, fileSize + recvFileSize);
//    close(fd);
//    // 传输成功
//    printf("get file success\n");
//    
//    return 0;
//}
//
//// 判断输入指令是否合法
//int isLegal(char *token){
//    for(int i = 0; i < instructCount; i++){
//        if(strcmp(token, instructSet[i]) == 0){
//            return 1;
//        }
//    }
//    return 0;
//}


// 下载子线程
void * handler(void *argc){
    downloadInfo_t * dInfo = (downloadInfo_t *)argc;

    // 读取进度
    int progressFd = open(dInfo->progressFileName, O_RDWR | O_CREAT , 0666);
    ERROR_CHECK(progressFd, -1, "open");
    int curFileSize = 0;
    if(lseek(progressFd, 0, SEEK_END) != 0){

        read(progressFd, &curFileSize, sizeof(int));
    }

    printf("getsize : %d\n", dInfo->getsSize);
    if(curFileSize >= dInfo->getsSize){
        // 这段已经下载完毕,直接退了即可
        pthread_exit(NULL);
    }

    // 建立通信
    int sockfd = establishConnect(dInfo->ip, dInfo->port);

    printf("sockfd :--%d--\n", sockfd);

    // 将文件的MD5发送给下载服务器，告诉下载服务器我要下载的文件，以及下载的位置以及要下载的大小
    TLVRequest(sockfd, dInfo->fileName, GETS);


    train_t mess;
    TLVRecvResponse(sockfd, &mess);
    if(mess.type == IS_NOT_OK){
        printf("gets fail\n");
        pthread_exit(NULL);
    }
    printf("%s\n", mess.buf);

    TLVRequestLen(sockfd, dInfo->begin + curFileSize);
    TLVRequestLen(sockfd, dInfo->getsSize - curFileSize);

    printf("begin + curFileSize: %d\n", dInfo->begin + curFileSize);
    printf("getsSize - curFileSize :%d\n", dInfo->getsSize - curFileSize);

    // 打开下载文件
    int fd = open(dInfo->clientPath, O_RDWR | O_CREAT, 0666);
    // 预留文件大小
    ftruncate(fd, dInfo->fileSumSize);

    char *buf = (char *)mmap(NULL, dInfo->fileSumSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if (buf == MAP_FAILED) {
        perror("mmap");
        pthread_exit(NULL);
    }


    printf("begin :%d\n", dInfo->begin);

    while(dInfo->getsSize - curFileSize > 0){
        int ret = recv(sockfd, buf + dInfo->begin + curFileSize, dInfo->getsSize - curFileSize, 0);

        ERROR_CHECK(ret, -1, "recv");

        curFileSize += ret;
        lseek(progressFd, 0, SEEK_SET);
        write(progressFd, &curFileSize, sizeof(int));
        /* printf("ret:--%d--\n", ret); */
        /* printf("dInfo->getsSize:--%d--\n", dInfo->getsSize - curFileSize); */
        /* sleep(1); */
    }

    printf("gets over\n");

    munmap(buf, dInfo->fileSumSize);
    close(fd);
    free(dInfo);

    pthread_exit(NULL);
}


// 功能：下载
int getsFile(int sockfd, char* clientPath, char* serverPath){

    int fd = open(clientPath, O_RDWR | O_CREAT, 0666);
    ERROR_CHECK(fd, -1, "open");
    // 功能：发送下载请求
    TLVRequest(sockfd, serverPath, GETS);

    train_t mess;
    TLVRecvResponse(sockfd, &mess);
    if(mess.type != IS_OK){
        printf("server talk: %s\n", mess.buf);
        return -1;
    }

    char fileName[1024];
    bzero(fileName, sizeof(fileName));
    strcpy(fileName, mess.buf);

    TLVRecvResponse(sockfd, &mess);
    // 需要下载文件的总大小
    int serverFileSize = atoi(mess.buf);

    printf("serverFileSize : %d\n", serverFileSize);

    // 查看文件大小，若已有部分文件，询问是否断点重传
    int fileSize = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET);

    close(fd);

    char ch[256] = {0};
    if(fileSize != 0){
        printf("Retransmission from breakpoint?[y/n]\n");
        scanf(" %s", ch);
        if(!strcmp(ch, "y")){
            fileSize = 0;
        }
    }

    // 接收下载服务器的IP和端口
    TLVRecvResponse(sockfd, &mess);
    // 测试能下载IP
    //
    // 理想情况：所有IP都能用
    //    char testIp[1024];
    //    bzero(testIp, sizeof(testIp));
    //
    //    strcpy(testIp, mess.buf);
    //
    //    int curUsedIPNum = 0;
    //    char *tempIp;
    //    char *tempPort;
    //    for(int i = 0; i < IPNUM; i++){
    //        
    //        if(i == 0){
    //            tempIp = strtok(testIp, "/");
    //        }else{
    //            tempIp = strtok(NULL, "/");
    //        }
    //        if(tempIp == NULL)break;
    //
    //        tempPort = strtok(NULL, "/");
    //
    //        int curSockfd = establishConnect(tempIp, tempPort);
    //        if(curSockfd <= 0){
    //            // 无法连接此下载服务器
    //            continue;
    //        }
    //        // 可以连接
    //
    //        curUsedIPNum ++;
    //        close(curSockfd);
    //    }
    //    // 无可用连接
    //    if(curUsedIPNum == 0){
    //        printf("gets fail\n");
    //        return -1;
    //   }

    // 切割IP建立连接,并将IP和端口号交给子线程，让子线程与下载服务器进行通信
    char *token;
    pthread_t pidSet[IPNUM];


    for(int i = 0; i < IPNUM; i++){
        // 填充下载信息子段
        downloadInfo_t *dInfo = (downloadInfo_t *)calloc(1, sizeof(downloadInfo_t));

        strcpy(dInfo->clientPath, clientPath);
        strcpy(dInfo->fileName, fileName);
        dInfo->fileSumSize = serverFileSize;

        char count[16];
        bzero(count, sizeof(count));
        sprintf(count, "%d", i);
        // 进度文件名
        char progressFile[1023];
        bzero(progressFile, sizeof(progressFile));
        strcat(progressFile, "netdisk_v3/");
        strcat(progressFile, fileName);
        // 拼接上文件名
        strcat(progressFile, "_progressFile");
        strcat(progressFile, count);

        strcpy(dInfo->progressFileName, progressFile);

        if(i == 0){
            token = strtok(mess.buf, "/");
        }else{
            token = strtok(NULL, "/");
        }
        if(token == NULL)break;

        memcpy(dInfo->ip, token, strlen(token));

        token = strtok(NULL, "/");
        if(token == NULL) break;
        memcpy(dInfo->port, token, strlen(token));

        // 均分
        int average = (serverFileSize) / IPNUM; 
        // 第i端的起始位置    
        dInfo->begin = average * i;
        if(i == IPNUM - 1){
            // 最后一段的传输大小
            dInfo->getsSize = serverFileSize - dInfo->begin;
        }else{
            dInfo->getsSize = average;
        }
        // 交给子线程下载文件
        pthread_create(&pidSet[i], NULL, handler, dInfo);
    }

    // 等待所有子线程结束下载
    for(int i = 0; i < IPNUM; i++){
        pthread_join(pidSet[i], NULL);
    }

    // 删除进度文件
    char curProgressName[1024] = {0};
    for(int i = 0; i < IPNUM; i++){
        sprintf(curProgressName, "netdisk_v3/%s_progressFile%d", fileName, i);
        remove(curProgressName);
    }

    // 传输成功
    printf("get file success\n");

    return 0;
}

// 判断输入指令是否合法
int isLegal(char *token){
    for(int i = 0; i < instructCount; i++){
        if(strcmp(token, instructSet[i]) == 0){
            return 1;
        }
    }
    return 0;
}



// 功能：指令处理
int dealInstruction(char *instruct,int sockfd){

    // 分割
    char *token = strtok(instruct, " \n");
    if(!isLegal(token)){
        printf("指令输入错误\n");
        return 0;
    }

    char *path1 = strtok(NULL, " \n");
    char *path2 = strtok(NULL, " \n");

    char *path3 = strtok(NULL, " \n");
    if(path3 != NULL){
        printf("argc num error\n");
        return -1;
    }

    printf("instruct : --%s--\n", token);
    printf("path1: --%s--\n", path1);
    printf("path2: --%s--\n", path2);


    // PWD
    if(!strcmp(token, "pwd") ){

        TLVRequest(sockfd, NULL, PWD);

    } else if(!strcmp(token, "cd") || !strcmp(token, "mkdir")){
        // 双指令处理
        if(path1 == NULL || path1[0] == '-'){
            printf("指令参数错误\n");
            return 0;
        }
        if(!strcmp(token, "cd")){
            TLVRequest(sockfd, path1, CD);
        }else if(!strcmp(token, "mkdir")){
            TLVRequest(sockfd, path1, MKDIR);
        }
        // ls单独处理
    }else if(!strcmp(token, "ls")){
        // 看是否有参数(改了，无参数)
        TLVRequest(sockfd, NULL, LS);
    }else if(!strcmp(token, "rmdir")){
        // rmdir 可能带参数，单独处理
        if(path1[0] == '-'){
            if(!strcmp(path1, "-r")){
                if(path2 == NULL){
                    printf("参数错误\n");
                    return 1;
                }else{
                    TLVRequest(sockfd, path2, RMDIR_R);
                }
            }else{
                printf("参数错误\n");
                return 1;
            }
        }
        // 不带参数
        if(path1 == NULL){
            printf("参数错误\n");
            return 1;
        }
        TLVRequest(sockfd, path1, RMDIR);
    }
    else{
        // 上传下载处理
        if(path1 == NULL || path2 == NULL){
            printf("指令参数错误\n");
            return -1;
        }

        if(!strcmp(token, "puts")){

            // 上传不存在的文件
            if(strcmp(token, "puts") == 0 && access(path1, F_OK) != 0){
                perror("puts");
                return -1;
            }

            // 上传文件，处理服务端响应
            putsFile(sockfd, path1, path2);
        }else{

            // 下载文件，以及处理服务段响应
            getsFile(sockfd, path1, path2);
        }
        return 0;
    }

    // 获取服务端的响应
    train_t mess;
    TLVRecvResponse(sockfd, &mess);
    printf("server talk:%s\n", mess.buf);
    return 0;
}



